﻿Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.ComponentModel
Imports System.Web.Script.Services
Imports Aricie.DNN.Modules.LuceneSearch.Business
Imports Lucene.Net.QueryParsers
Imports Lucene.Net.Search
Imports Lucene.Net.Index
Imports Aricie.DNN.Services
Imports Aricie.Services
Imports System.Reflection
Imports Aricie.DNN.Modules.LuceneSearch.Settings
Imports Aricie.DNN.Settings
Imports Aricie.Collections
Imports System.Linq
Imports DotNetNuke.Entities.Users
Imports DotNetNuke.Services.Localization

' To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
<System.Web.Script.Services.ScriptService()> _
<System.Web.Services.WebService(Namespace:="http://tempuri.org/")> _
<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<ToolboxItem(False)> _
Public Class Aricie_LS_AutocompleteSvc
    Inherits System.Web.Services.WebService

    Private Shared m_PropertiesSearchResult As Dictionary(Of String, PropertyInfo)
    Private Shared m_StaticParser As QueryParser

    Shared Sub New()
        m_PropertiesSearchResult = ReflectionHelper.GetPropertiesDictionary(Of LuceneSearchResultsInfo)()
        ' on ne veut pas de stop words
        m_StaticParser = New QueryParser(Aricie.DNN.Modules.LuceneSearch.Common.Constants.LuceneCurrentVersion, String.Empty, SpecificVersionLuceneProvider.Instance().GetStandardAnalyzer())
        m_StaticParser.MultiTermRewriteMethod = MultiTermQuery.SCORING_BOOLEAN_QUERY_REWRITE
    End Sub

    <WebMethod(EnableSession:=True)> _
    <ScriptMethod(ResponseFormat:=ResponseFormat.Json)> _
    Public Function GetAutocompleteItems(ByVal prefix As String, ByVal culture As String) As Object
        Return GetAutocompleteItemswModuleId(prefix, Aricie.DNN.Modules.LuceneSearch.Common.Constants.SearchAndResultItemInfos.ModuleIdCannotBeDetermined, culture)
    End Function

    <WebMethod(EnableSession:=True)> _
    <ScriptMethod(ResponseFormat:=ResponseFormat.Json)> _
    Public Function GetAutocompleteItemswModuleId(ByVal prefix As String, ByVal ModuleId As Integer, ByVal culture As String) As Object

        ' si on a un préfixe, décodons le et transformons un peu son contenu
        If Not String.IsNullOrEmpty(prefix) Then
            prefix = HttpUtility.UrlDecode(prefix, System.Text.Encoding.Default)
            ' on force la requête à passer une première fois dans un parser pour nous permettre d'avoir quelque chose qui gère les préfixes correctement
            Dim resultQuery As Query = m_StaticParser.Parse(QueryParser.Escape(prefix))
            If TypeOf resultQuery Is PhraseQuery Then
                ' supprimer les quotes rajoutées
                prefix = resultQuery.ToString().TrimStart(New Char() {""""c}).TrimEnd(New Char() {""""c})
            Else
                prefix = resultQuery.ToString()
            End If
            prefix = prefix.Trim.ToLower
        End If

        ' forçons DNN à savoir quel utilisateur est actif
        If (System.Threading.Thread.CurrentPrincipal.Identity.IsAuthenticated) Then
            HttpContext.Current.Items("UserInfo") = UserController.GetUserByName(NukeHelper.PortalId, System.Threading.Thread.CurrentPrincipal.Identity.Name)
        End If

        ' récupérons les informations sur le module
        Dim moduleInfo = GetModuleInfo(ModuleId)
        If moduleInfo Is Nothing Then
            Throw New InvalidOperationException("No Lucenesearch results module has been configured on the portal; please configure a Lucenesearch module")
        End If

        ' vérification de notre activation
        Dim settings As LucenePortalSettings = GetModuleSettings(Of LucenePortalSettings)(SettingsScope.PortalSettings, NukeHelper.PortalId)

        If settings Is Nothing OrElse settings.Autocomplete Is Nothing Then
            Throw New InvalidOperationException("Autocomplete webservice configuration cannot be accessed; please check error logs")
        End If
        Dim ACS As AutoComplete = settings.Autocomplete

        If Not ACS.EnableAutoComplete Then
            Throw New InvalidOperationException("Autocomplete webservice is not enabled; enable it in the Aricie.LuceneSearch Indexer settings")
        End If

        Dim output As New List(Of Dictionary(Of String, Object))

        Try
            Dim PQD As PortalQueryingDriver = PortalQueryingDriver.GetCurrent()

            Dim fieldFilter As New BooleanQuery
            Dim splitValues As String() = prefix.Split(New String() {" "}, StringSplitOptions.RemoveEmptyEntries)

            For Each Field As FieldDefinition In PQD.GetSearchFields(moduleInfo.ModuleID)
                Dim bq As New BooleanQuery()
                For Each value As String In splitValues.Take(splitValues.Length - 1)
                    bq.Add(New TermQuery(New Term(Field.Id, value)), Occur.MUST)
                Next
                Dim PQ As New PrefixQuery(New Term(Field.Id, splitValues.Last()))
                PQ.RewriteMethod = MultiTermQuery.SCORING_BOOLEAN_QUERY_REWRITE
                bq.Add(PQ, Occur.MUST)
                fieldFilter.Add(bq, Occur.SHOULD)
            Next

            Dim SearchResults As LuceneQueryResults = PQD.RunQuery(NukeHelper.PortalId _
                                                                   , moduleInfo.ModuleID _
                                                                   , New LuceneFilterQuery With {.FilterQuery = fieldFilter} _
                                                                   , String.Join(",", (From sField In ACS.SortingFields Select sField.SearchString).ToArray()) _
                                                                   , 0 _
                                                                   , ACS.ReturnSetSize)

            For Each RSII As LuceneSearchResultsInfo In SearchResults.AsSearchResultsCollection(moduleInfo.ModuleID, False)
                Dim CurrentResult As New Dictionary(Of String, Object)
                For Each OutputField As String In ACS.OutputFields.Distinct() 'on ne veut pas de champ en double
                    If (m_PropertiesSearchResult.ContainsKey(OutputField)) Then
                        AddEntryToResult(CurrentResult, RSII, OutputField)
                    End If
                Next
                ' on rajoute toujours la fullurl pour renvoyer l'utilisateur sur l'item correcte
                If (Not ACS.OutputFields.Contains("GoToFullUrl")) Then
                    AddEntryToResult(CurrentResult, RSII, "GoToFullUrl")
                End If
                ' on rajoute toujours l'item de grouping
                If (ACS.EnableGrouping AndAlso Not ACS.OutputFields.Contains(ACS.GroupName)) Then
                    AddEntryToResult(CurrentResult, RSII, ACS.GroupName)
                End If
                output.Add(CurrentResult)
            Next
        Catch ex As Exception
            Throw New InvalidOperationException("Error during search") 'TODO: better error management
        End Try

        'Serialisation directe pour cause de mauvais retour sur Serialisation JSON. Pas de débrayage sauf à savoir que c'est entièrement du Dictionary<string, string>
        'http://stackoverflow.com/questions/7600562/restful-asp-net-webservice-how-can-i-return-json-serialized-dictionarystring
        Dim serializer As New Script.Serialization.JavaScriptSerializer()
        Return serializer.Serialize(output)

    End Function

    Private Sub AddEntryToResult(ByVal CurrentResult As Dictionary(Of String, Object), ByVal target As LuceneSearchResultsInfo, ByVal propertyName As String)
        Try

            Dim FinalValue As Object = Nothing

            FinalValue = m_PropertiesSearchResult(propertyName).GetValue(target, Nothing)

            CurrentResult.Add(propertyName, FinalValue)

        Catch ex As Exception
            CurrentResult.Add(propertyName, String.Format("Error while trying to access the ""{0}"" field", propertyName))
            Aricie.Services.ExceptionHelper.LogException(ex)
        End Try

    End Sub

    Private Function GetModuleInfo(ByVal ModuleId As Integer) As DotNetNuke.Entities.Modules.ModuleInfo
        If (ModuleId = Aricie.DNN.Modules.LuceneSearch.Common.Constants.SearchAndResultItemInfos.ModuleIdCannotBeDetermined) Then
            Return NukeHelper.ModuleController.GetModuleByDefinition(PortalSettings.PortalId, Aricie.DNN.Modules.LuceneSearch.Common.Constants.ModuleNameLuceneSearchResults)
        Else
            Return NukeHelper.ModuleController.GetModule(ModuleId, DotNetNuke.Common.Utilities.Null.NullInteger)
        End If
    End Function

    ' Conservé pour POC: extraction des termes de recherche correspondant à un préfixe
    'Private Sub POCTopTerms()
    '    Dim PQD2 As PortalQueryingDriver = PortalQueryingDriver.GetCurrent()
    '    Dim bq As New BooleanQuery()
    '    For Each fd As FieldDefinition In PQD2.GetSearchFields(moduleInfo.ModuleID)
    '        Dim pq As New PrefixQuery(New Term(fd.Id, prefix))
    '        pq.SetRewriteMethod(PrefixQuery.SCORING_BOOLEAN_QUERY_REWRITE)
    '        Dim q As Query = pq.Rewrite(PortalQueryingDriver.GetByPortalId(PortalId).GetIndexReader(True))
    '        bq.Add(q, Occur.SHOULD)
    '    Next

    '    'Dim q As Query = bq.Rewrite(PortalQueryingDriver.GetByPortalId(PortalId).GetIndexReader(True))
    '    Dim HT As New Hashtable
    '    bq.ExtractTerms(HT)


    'End Sub

End Class